﻿using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using BoYuanCore.Framework.StringUtility;
using JWT;
using JWT.Algorithms;
using JWT.Exceptions;
using JWT.Serializers;
using KeXin.Model.Config;
using Microsoft.IdentityModel.Tokens;
using JwtRegisteredClaimNames = Microsoft.IdentityModel.JsonWebTokens.JwtRegisteredClaimNames;

namespace KeXin.Service
{
    public class JwtHelper
    {
        /// <summary>
        /// 生成token
        /// </summary>
        /// <param name="claims"></param>
        /// <param name="config">配置项</param>
        /// <returns></returns>
        public static string CreateToken(IEnumerable<Claim> claims,JwtConfig config)
        {
            SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config.SecurityKey));//数字签名信息部分
            // 创建token
            var token = new JwtSecurityToken(
                issuer: config.Issuer, //issuer代表颁发Token的Web应用程序 【MyBlog.JWT】
                audience: config.Audience, //audience是Token的受理者 【MyBlog.Api】
                claims: claims,
                notBefore: DateTime.Now,
                expires: DateTime.Now.AddMinutes(config.Expires),//多少分钟后过期
                signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)//加密后返回
            );

            /*
              如果报异常 IDX10720: Unable to create KeyedHashAlgorithm for algorithmHS256'. the key size must be greater than: '256' bits, key has '152' bits. Arg ParamName Name
                这个问题出现是在升级自己项目.NET8版本出现的；升级后重新登陆 jwt创建token 报错，意思是密钥需要超过 256 bit，即 设定得密钥太短了
                需要加长config.SecurityKey 。 参考： https://www.cnblogs.com/Zeng02/p/17933664.html
             */

            var jwtToken = new JwtSecurityTokenHandler().WriteToken(token);
            return jwtToken;
        }

        /// <summary>
        /// 生成token
        /// </summary>
        /// <param name="claims"></param>
        /// <param name="config">配置项</param>
        /// <returns></returns>
        public static string GetRefreshToken(IEnumerable<Claim> claims,JwtConfig config)
        {
            SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config.RefreshSecurityKey));//数字签名信息部分
            // 创建RefreshToken, 不设置issuer和audience，这样RefreshToken不会冒充accessToken尔参与到业务请求中
            var token = new JwtSecurityToken(
                //issuer: config.Issuer, //issuer代表颁发Token的Web应用程序 【MyBlog.JWT】
                //audience: config.Audience, //audience是Token的受理者 【MyBlog.Api】
                claims: claims,
                //notBefore: DateTime.Now,
                expires: DateTime.Now.AddMinutes(config.RefreshExpires),//多少分钟后过期
                signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)//注意此加密算法跟accessToken加密算法不一样。为了防止用RefreshToken去做业务请求
            );
            var jwtToken = new JwtSecurityTokenHandler().WriteToken(token);
            return jwtToken;
        }

        /// <summary>
        /// 刷新并获取最新的AccessToken
        /// </summary>
        /// <returns></returns>
        public static string RefreshAccessToken(string refreshToken, JwtConfig config)
        {
            try
            {
                //验证refreshToken。不符合或验证失败 则抛异常
                var handler = new JwtSecurityTokenHandler();
                var cp= handler.ValidateToken(refreshToken, new TokenValidationParameters
                {
                    ValidateLifetime = true,//验证生命周期
                    ValidateAudience = false,
                    ValidateIssuer = false,
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(config.RefreshSecurityKey)),
                }, out SecurityToken validatedToken);

                //获取新的accessToken
                return CreateToken(cp.Claims, config);//从验证refreshToken中Claims赋值给accessToken
            }
            catch (Exception ex)
            {
                return null;
            }
        }

        /// <summary>
        /// 创建token
        /// </summary>
        /// <returns></returns>
        public static string CreateJwtToken(IDictionary<string, object> payload, string secret, IDictionary<string, object> extraHeaders = null)
        {
            IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
            IJsonSerializer serializer = new JsonNetSerializer();
            IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
            IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
            var token = encoder.Encode(payload, secret);
            return token;
        }

        /// <summary>
        /// 校验解析token
        /// </summary>
        /// <returns></returns>
        public static (string,string) ValidateJwtToken(string token, string secret)
        {
            // 参考 https://blog.csdn.net/tisyact/article/details/110195980
            //上面的RefreshAccessToken方法 就有刷新token验证

            try
            {
                IJsonSerializer serializer = new JsonNetSerializer();
                IDateTimeProvider provider = new UtcDateTimeProvider();
                IJwtValidator validator = new JwtValidator(serializer, provider);
                IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
                IJwtAlgorithm alg = new HMACSHA256Algorithm();
                IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder, alg);
                var json = decoder.Decode(token, secret, true);
                //校验通过，返回解密后的字符串
                return (json,string.Empty);
            }
            catch (TokenExpiredException)
            {
                //表示过期
                return (string.Empty,"expired");
            }
            catch (SignatureVerificationException)
            {
                //表示验证不通过
                return (string.Empty,"invalid");
            }
            catch (Exception)
            {
                return (string.Empty,"error");
            }
        }

    }
}
/*
accessToken是无状态的，但是为了安全一般会有时效性。accessToken是没有退出登陆设定(如果未失效状态token,需要设定为失效,需要用状态形式来维护token,这个跟token无状态设计相违背)

RefreshToken 是为了换取accessToken，以便防止accessToken失效导致再频繁登陆获取token的问题。

accessToken也可以换取新accessToken。个人认为RefreshToken不能请求业务接口，这样不安全。
 */